package no.ffi.gismoidm.pubsub;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import no.ffi.gismoidm.attrex.AttributeEvaluationException;
import no.ffi.gismoidm.attrex.BooleanValue;
import no.ffi.gismoidm.attrex.BooleanOperand;
import no.ffi.gismoidm.attrex.Parser;
import no.ffi.gismoidm.attrex.TrueAlways;

/**
 * Representation of a Subscription and sent from a subscriber to a message router (MR)
 * The object is sent over an authenticated channel, why the identity statement is not
 * necessary to include
 * @author anders
 */
public class Subscription implements java.io.Serializable {
    private BooleanOperand publisherRequirements;  // Required attributes of the publisher
    private List<SubscriptionTopic> topics;
    private long reference;
    private boolean delete; // true indicates a delete operation
    
    //public Subscription() {}
    
    public Subscription(List<SubscriptionTopic> topics, BooleanOperand pubReq) {
        this.topics = topics;
        if (pubReq == null) pubReq = new TrueAlways();
        publisherRequirements = pubReq;
        reference = System.currentTimeMillis();
        delete = false;
    }
    
    public Subscription(SubscriptionTopic topic, BooleanOperand pubReq) {
        ArrayList<SubscriptionTopic> list = new ArrayList();
        list.add(topic);
        this.topics = list;
        if (pubReq == null) pubReq = new TrueAlways();
        publisherRequirements = pubReq;
        reference = System.currentTimeMillis()/1000 + (long)(Math.random()*1000);
        delete = false;
    }
    
    public Subscription(String topic, BooleanOperand pubReq) {
        this(new SubscriptionTopic(topic),pubReq);
    }
    
    /* Constructor for a subscription delete operation */
    public Subscription(boolean delete) {
        delete = true;
    }
    
    public long getReference() { return reference; }
    public boolean getDelete() { return delete; }
    
    /*
     * Check that (1) Topics of publication and subscription match,
     * (2) that subscriber's attributes match the requirement set by the publisher
     * (3) the other way around
     * These requirements must be met in order for an MR to deliver a message
     * the a subscriber
     */
    public boolean publicationMatch(Publication pub, Properties subscriberAttributes) {
        try {
            // Check of topics match
            if (!topicMatch(pub)) return false;
            // Check subscriber attributes
            if (!subscriberRequirementsMet(pub, subscriberAttributes)) return false;
            // Check sender's authorization
            if (!senderIsAuthorized(pub, subscriberAttributes)) return false;
            // Check publisher attributes
            return publisherRequirementsMet(pub);
        } catch (Exception e) { e.printStackTrace(); }
        return false;
    }
    
    /*
     * In order to forward a message to another MR, the topic must be matching
     * and the publisher requirements must be met. The subscription requirements
     * are not hecked at this stage.
     */
    public boolean forwardMatch(Publication pub) {
        try {
            // Check of topics match
            if (!topicMatch(pub)) return false;
            // Check publisher attributes
            return publisherRequirementsMet(pub);
        } catch (Exception e) { e.printStackTrace(); }
        return false;
    }

    private boolean topicMatch(Publication pub) {    
        // Check each subscription topic
        Iterator<SubscriptionTopic> it = topics.iterator();
        while (it.hasNext()) {
            SubscriptionTopic st = it.next();
            if (st.matchTopics(pub.getPublicationTopics()))
                return true;
        }
        return false;
    }
    
    private boolean publisherRequirementsMet(Publication pub) throws AttributeEvaluationException {
        // Check that publisher meets the publishing requirements
        BooleanValue b = 
                publisherRequirements.getBooleanValue(pub.getPublisherAttributes());
        return b.truthValue();
    }
    
    private boolean subscriberRequirementsMet(Publication pub, Properties subscriberAttributes) 
            throws AttributeEvaluationException {
        // Check that subscriber meets the delivery requirements
        BooleanOperand ev = pub.getSubscriberRequirements();
        BooleanValue b = 
                ev.getBooleanValue(subscriberAttributes);
        return b.truthValue();
    }
    
    private boolean senderIsAuthorized(Publication pub, Properties subscriberAttributes) 
            throws AttributeEvaluationException {
        Properties senderAttributeSet = pub.getIdentityStatement().getAttributeAsProperties();
        String auth = senderAttributeSet.getProperty("PublishingAuthorization");
        if (auth==null) return true; // No restriction on sender
        try {
            BooleanOperand authRule = Parser.parse(auth);
            BooleanValue b = authRule.getBooleanValue(senderAttributeSet);
            return b.truthValue();
        } catch (Exception e) { throw new AttributeEvaluationException(e.getMessage()); }
    }
}

